#include "SQLSelect.h"

#include "../../../utils/instanceof.h"
#include "SQLUnionQuery.h"
#include "SQLSelectQueryBlock.h"
#include "../expr/SQLAllColumnExpr.h"
#include "SQLWithSubqueryClause.h"
#include "../../visitor/SQLASTVisitor.h"
#include "../../../utils/listAddAll.h"
#include "../../../utils/log.h"

SQLSelect::SQLSelect()
{
  hints = std::make_shared<std::list<SQLHint_ptr>>();
  forXmlOptions = std::make_shared<std::list<string_ptr>>();
}

SQLHint_list_ptr SQLSelect::getHints()
{
  // if (hints == NULL) {
  //     hints = new ArrayList<SQLHint*>(2);
  // }
  return hints;
}

int SQLSelect::getHintsSize()
{
  // if (hints == NULL) {
  //     return 0;
  // }
  return hints->size();
}

SQLSelect::SQLSelect(SQLSelectQuery_ptr query)
{
  this->setQuery(query);
}

SQLWithSubqueryClause_ptr SQLSelect::getWithSubQuery()
{
  return withSubQuery;
}

void SQLSelect::setWithSubQuery(SQLWithSubqueryClause_ptr x)
{
  if (x != NULL)
  {
    x->setParent(SharedObject(SQLSelect));
  }
  this->withSubQuery = x;
}

SQLSelectQuery_ptr SQLSelect::getQuery()
{
  return this->query;
}

void SQLSelect::setQuery(SQLSelectQuery_ptr query)
{
  if (query != NULL)
  {
    query->setParent(SharedObject(SQLSelect));
  }
  this->query = query;
}

SQLSelectQueryBlock_ptr SQLSelect::getQueryBlock()
{
  if (instanceof <SQLSelectQuery, SQLSelectQueryBlock>(query))
  {
    return std::dynamic_pointer_cast<SQLSelectQueryBlock>(query);
  }

  return NULL;
}

SQLOrderBy_ptr SQLSelect::getOrderBy()
{
  return this->orderBy;
}

void SQLSelect::setOrderBy(SQLOrderBy_ptr orderBy)
{
  if (orderBy != NULL)
  {
    orderBy->setParent(SharedObject(SQLSelect));
  }
  this->orderBy = orderBy;
}

void SQLSelect::accept0(SQLASTVisitor_ptr v)
{
  if (v->visit(SharedObject(SQLSelect)))
  {
    if (withSubQuery != NULL)
    {
      withSubQuery->accept0(v);
    }

    if (this->query != NULL)
    {
      this->query->accept(v);
    }

    if (this->restriction != NULL)
    {
      this->restriction->accept(v);
    }

    if (this->orderBy != NULL)
    {
      this->orderBy->accept(v);
    }

    if (!this->hints->empty())
    {
      for (SQLHint_ptr hint : *hints)
      {
        hint->accept(v);
      }
    }

    if (this->offset != NULL)
    {
      this->offset->accept(v);
    }

    if (this->rowCount != NULL)
    {
      this->rowCount->accept(v);
    }

    if (this->headHint != NULL)
    {
      this->headHint->accept(v);
    }
  }

  v->endVisit(SharedObject(SQLSelect));
}

//     DbType SQLSelect::getDbType() {
//         DbType dbType = NULL;

//         SQLObject parent = this->getParent();
//         if (parent instanceof SQLStatement) {
//             dbType = ((SQLStatement) parent).getDbType();
//         }

//         if (dbType == NULL && parent instanceof OracleSQLObject) {
//             dbType = DbType.oracle;
//         }

//         if (dbType == NULL && query instanceof SQLSelectQueryBlock) {
//             dbType = ((SQLSelectQueryBlock) query).dbType;
//         }

//         return dbType;
//     }

// SQLSelect_ptr SQLSelect::clone()
SQLObject_ptr SQLSelect::clone()
{
  SQLObject_ptr clone_tmp = std::shared_ptr<SQLSelect>(new SQLSelect());
  std::shared_ptr<SQLSelect> clone_res = std::dynamic_pointer_cast<SQLSelect>(clone_tmp);

  clone_res->withSubQuery = this->withSubQuery;
  if (query != NULL)
  {
    clone_res->setQuery(std::dynamic_pointer_cast<SQLSelectQuery>(query->clone()));
  }

  if (orderBy != NULL)
  {
    clone_res->setOrderBy(std::dynamic_pointer_cast<SQLOrderBy>(this->orderBy->clone()));
  }
  if (restriction != NULL)
  {
    clone_res->setRestriction(restriction->clone());
  }

  if (this->hints != NULL)
  {
    for (SQLHint_ptr hint : *this->hints)
    {
      clone_res->hints->push_back(hint);
    }
  }

  clone_res->forBrowse = forBrowse;

  if (forXmlOptions != NULL)
  {
    // clone_res->forXmlOptions = new ArrayList<string_ptr >(forXmlOptions);
    for (auto it : *forXmlOptions)
    {
      clone_res->forXmlOptions->push_back(it);
    }
  }

  if (xmlPath != NULL)
  {
    clone_res->setXmlPath(std::dynamic_pointer_cast<SQLExpr>(xmlPath->clone()));
  }

  if (rowCount != NULL)
  {
    clone_res->setRowCount(std::dynamic_pointer_cast<SQLExpr>(rowCount->clone()));
  }

  if (offset != NULL)
  {
    clone_res->setOffset(std::dynamic_pointer_cast<SQLExpr>(offset->clone()));
  }

  if (headHint != NULL)
  {
    clone_res->setHeadHint(std::dynamic_pointer_cast<SQLHint>(headHint->clone()));
  }

  return clone_res;
}

//     BOOL_ptr SQLSelect::isSimple() {
//         return withSubQuery == NULL
//                 && (hints == NULL || hints.isEmpty())
//                 && restriction == NULL
//                 && (!forBrowse)
//                 && (forXmlOptions == NULL || forXmlOptions.isEmpty())
//                 && xmlPath == NULL
//                 && rowCount == NULL
//                 && offset == NULL;
//     }

SQLObject_ptr SQLSelect::getRestriction()
{
  return this->restriction;
}

void SQLSelect::setRestriction(SQLObject_ptr restriction)
{
  if (restriction != NULL)
  {
    restriction->setParent(SharedObject(SQLSelect));
  }
  this->restriction = restriction;
}

BOOL_ptr SQLSelect::isForBrowse()
{
  return forBrowse;
}

void SQLSelect::setForBrowse(BOOL_ptr forBrowse)
{
  this->forBrowse = forBrowse;
}

string_list_ptr SQLSelect::getForXmlOptions()
{
  // if (forXmlOptions == NULL) {
  //     forXmlOptions = new ArrayList<string_ptr >(4);
  // }

  return forXmlOptions;
}

int SQLSelect::getForXmlOptionsSize()
{
  // if (forXmlOptions == NULL) {
  //     return 0;
  // }
  return forXmlOptions->size();
}

SQLExpr_ptr SQLSelect::getRowCount()
{
  return rowCount;
}

void SQLSelect::setRowCount(SQLExpr_ptr rowCount)
{
  if (rowCount != NULL)
  {
    rowCount->setParent(SharedObject(SQLSelect));
  }

  this->rowCount = rowCount;
}

SQLHint_ptr SQLSelect::getHeadHint()
{
  return headHint;
}

void SQLSelect::setHeadHint(SQLHint_ptr headHint)
{
  this->headHint = headHint;
}

SQLExpr_ptr SQLSelect::getOffset()
{
  return offset;
}

void SQLSelect::setOffset(SQLExpr_ptr offset)
{
  if (offset != NULL)
  {
    offset->setParent(SharedObject(SQLSelect));
  }
  this->offset = offset;
}

SQLExpr_ptr SQLSelect::getXmlPath()
{
  return xmlPath;
}

void SQLSelect::setXmlPath(SQLExpr_ptr xmlPath)
{
  if (xmlPath != NULL)
  {
    xmlPath->setParent(SharedObject(SQLSelect));
  }
  this->xmlPath = xmlPath;
}

string_list_ptr SQLSelect::computeSelecteListAlias()
{
  SQLSelectQueryBlock_ptr firstQuery = getFirstQueryBlock();
  if (firstQuery != NULL)
  {
    return firstQuery->computeSelecteListAlias();
  }

  string_list_ptr tmp = std::make_shared<std::list<string_ptr>>();
  return tmp;
}

//     SQLSelectQueryBlock SQLSelect::getFirstQueryBlock() {
//         if (query instanceof SQLSelectQueryBlock) {
//             return (SQLSelectQueryBlock) query;
//         }

//         if (query instanceof SQLUnionQuery) {
//             SQLUnionQuery union = (SQLUnionQuery) query;
//             while (union.getLeft() instanceof SQLUnionQuery) {
//                 union = (SQLUnionQuery) union.getLeft();
//             }
//             return union.getFirstQueryBlock();
//         }

//         return NULL;
//     }

bool SQLSelect::equals(Object_ptr o)
{
  if (this == o.get())
  {
    return true;
  }
  if (o == NULL || getClass() != o->getClass())
  {
    return false;
  }

  std::shared_ptr<SQLSelect> sqlSelect = std::dynamic_pointer_cast<SQLSelect>(o);

  if (forBrowse != sqlSelect->forBrowse)
  {
    return false;
  }
  // if (withSubQuery != NULL ? !withSubQuery.equals(sqlSelect->withSubQuery) : sqlSelect->withSubQuery != NULL) {
  //     return BOOL::FALSE;
  // }
  if (query != NULL ? !(query == sqlSelect->query) : sqlSelect->query != NULL)
  {
    return false;
  }
  // if (orderBy != NULL ? !orderBy.equals(sqlSelect.orderBy) : sqlSelect.orderBy != NULL) {
  //     return BOOL::FALSE;
  // }
  // if (limit != NULL ? !limit.equals(sqlSelect.limit) : sqlSelect.limit != NULL) {
  //     return BOOL::FALSE;
  // }
  // if (hints != NULL ? !hints.equals(sqlSelect.hints) : sqlSelect.hints != NULL) {
  //     return BOOL::FALSE;
  // }
  // if (restriction != NULL ? !restriction.equals(sqlSelect.restriction) : sqlSelect.restriction != NULL) {
  //     return BOOL::FALSE;
  // }
  // if (forXmlOptions != NULL ? !forXmlOptions.equals(sqlSelect.forXmlOptions) : sqlSelect.forXmlOptions != NULL) {
  //     return BOOL::FALSE;
  // }
  // if (xmlPath != NULL ? !xmlPath.equals(sqlSelect.xmlPath) : sqlSelect.xmlPath != NULL) {
  //     return BOOL::FALSE;
  // }
  // if (rowCount != NULL ? !rowCount.equals(sqlSelect.rowCount) : sqlSelect.rowCount != NULL) {
  //     return BOOL::FALSE;
  // }
  // if (offset != NULL ? !offset.equals(sqlSelect.offset) : sqlSelect.offset != NULL) {
  //     return BOOL::FALSE;
  // }
  return headHint != NULL ? headHint == sqlSelect->headHint : sqlSelect->headHint == NULL;
}

// bool SQLSelect::equals(Object *o)
// {
//   if (this == o)
//   {
//     return true;
//   }
//   if (o == NULL || getClass() != o->getClass())
//   {
//     return false;
//   }

//   SQLSelect_ptr sqlSelect = SQLSelect_ptr(dynamic_cast<SQLSelect *>(o));

//   if (forBrowse != sqlSelect->forBrowse)
//   {
//     return false;
//   }
//   // if (withSubQuery != NULL ? !withSubQuery.equals(sqlSelect->withSubQuery) : sqlSelect->withSubQuery != NULL) {
//   //     return BOOL::FALSE;
//   // }
//   if (query != NULL ? !(query == sqlSelect->query) : sqlSelect->query != NULL)
//   {
//     return false;
//   }
//   // if (orderBy != NULL ? !orderBy.equals(sqlSelect.orderBy) : sqlSelect.orderBy != NULL) {
//   //     return BOOL::FALSE;
//   // }
//   // if (limit != NULL ? !limit.equals(sqlSelect.limit) : sqlSelect.limit != NULL) {
//   //     return BOOL::FALSE;
//   // }
//   // if (hints != NULL ? !hints.equals(sqlSelect.hints) : sqlSelect.hints != NULL) {
//   //     return BOOL::FALSE;
//   // }
//   // if (restriction != NULL ? !restriction.equals(sqlSelect.restriction) : sqlSelect.restriction != NULL) {
//   //     return BOOL::FALSE;
//   // }
//   // if (forXmlOptions != NULL ? !forXmlOptions.equals(sqlSelect.forXmlOptions) : sqlSelect.forXmlOptions != NULL) {
//   //     return BOOL::FALSE;
//   // }
//   // if (xmlPath != NULL ? !xmlPath.equals(sqlSelect.xmlPath) : sqlSelect.xmlPath != NULL) {
//   //     return BOOL::FALSE;
//   // }
//   // if (rowCount != NULL ? !rowCount.equals(sqlSelect.rowCount) : sqlSelect.rowCount != NULL) {
//   //     return BOOL::FALSE;
//   // }
//   // if (offset != NULL ? !offset.equals(sqlSelect.offset) : sqlSelect.offset != NULL) {
//   //     return BOOL::FALSE;
//   // }
//   return headHint != NULL ? headHint == sqlSelect->headHint : sqlSelect->headHint == NULL;
// }

int SQLSelect::hashCode()
{
  int result = withSubQuery != NULL ? withSubQuery->hashCode() : 0;
  result = 31 * result + (query != NULL ? query->hashCode() : 0);
  result = 31 * result + (orderBy != NULL ? orderBy->hashCode() : 0);
  result = 31 * result + (limit != NULL ? limit->hashCode() : 0);
  // result = 31 * result + (hints != NULL ? hints.hashCode() : 0);
  result = 31 * result + (restriction != NULL ? restriction->hashCode() : 0);
  result = 31 * result + (forBrowse ? 1 : 0);
  // result = 31 * result + (forXmlOptions != NULL ? forXmlOptions.hashCode() : 0);
  result = 31 * result + (xmlPath != NULL ? xmlPath->hashCode() : 0);
  result = 31 * result + (rowCount != NULL ? rowCount->hashCode() : 0);
  result = 31 * result + (offset != NULL ? offset->hashCode() : 0);
  result = 31 * result + (headHint != NULL ? headHint->hashCode() : 0);
  return result;
}

BOOL_ptr SQLSelect::addWhere(SQLExpr_ptr where)
{
  if (where == NULL)
  {
    return BOOL::FALSE;
  }

  if (instanceof <SQLSelectQuery, SQLSelectQueryBlock>(query))
  {
    (std::dynamic_pointer_cast<SQLSelectQueryBlock>(query))->addWhere(where);
    return BOOL::TRUE;
  }

  if (instanceof <SQLSelectQuery, SQLUnionQuery>(query))
  {
    SQLSelectQueryBlock_ptr queryBlock = SQLSelectQueryBlock_ptr(new SQLSelectQueryBlock(getDbType()));
    queryBlock->setFrom(SQLSelect_ptr(new SQLSelect(query)), make_string_ptr("u"));
    LOG_INFO << __FUNCTION__ << ": queryBlock->addSelectItem";
    queryBlock->addSelectItem(SQLAllColumnExpr_ptr(new SQLAllColumnExpr()));
    queryBlock->setParent(queryBlock);
    query = queryBlock;
    return BOOL::TRUE;
  }

  return BOOL::FALSE;
}

BOOL_ptr SQLSelect::replace(SQLSelectQuery_ptr cmp, SQLSelectQuery_ptr target)
{
  if (cmp == query)
  {
    setQuery(target);
    return BOOL::TRUE;
  }

  return BOOL::FALSE;
}

SQLLimit_ptr SQLSelect::getLimit()
{
  return limit;
}

void SQLSelect::setLimit(SQLLimit_ptr x)
{
  if (x != nullptr)
  {
    x->setParent(SharedObject(SQLSelect));
  }
  this->limit = x;
}

SQLSelectQueryBlock_ptr SQLSelect::getFirstQueryBlock()
{
  if (instanceof <SQLSelectQuery, SQLSelectQueryBlock>(query))
  {
    return std::dynamic_pointer_cast<SQLSelectQueryBlock>(query);
  }

  if (instanceof <SQLSelectQuery, SQLUnionQuery>(query))
  {
    std::shared_ptr<SQLUnionQuery> union_ = std::dynamic_pointer_cast<SQLUnionQuery>(query);
    while (instanceof <SQLSelectQuery, SQLUnionQuery>(union_->getLeft()))
    {
      union_ = std::dynamic_pointer_cast<SQLUnionQuery>(union_->getLeft());
    }
    return union_->getFirstQueryBlock();
  }

  return NULL;
}